package lxs.swift.operation;


import lxs.swift.collector.wrap.Option;
import lxs.swift.collector.wrap.Trouble;
import lxs.swift.fn.Consume;
import lxs.swift.fn.Func;
import lxs.swift.fn.Run;
import lxs.swift.fn.Supply;
import lxs.swift.tool.Errors;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;
import java.util.function.Function;


public class Try<T, E extends Throwable> {
    private final Supply<T, E> source;
    private volatile boolean executed = false;

    private Try(Supply<T, E> source) {
        this.source = source;
    }

    private void assertExecuted() {
        if (executed) throw new IllegalStateException("Try has already been executed");
    }

    public <R> Try<R, E> then(@NotNull Func<T, R, E> fn) {
        Objects.requireNonNull(fn);
        assertExecuted();
        return new Try<>(() -> {
            this.executed = true;
            T t = source.get();
            return fn.apply(t);
        });
    }

    public <R> RunTry<E> then(@NotNull Consume<T, E> consume) {
        Objects.requireNonNull(consume);
        assertExecuted();
        return new RunTry<>(() -> {
            this.executed = true;
            T t = source.get();
            consume.accept(t);
        });
    }

    public Try<T, E> caught(@NotNull Func<E, T, E> f) {
        Objects.requireNonNull(f);
        assertExecuted();
        return new Try<>(() -> {
            this.executed = true;
            try {
                return source.get();
            } catch (Throwable e) {
                return f.apply(((E) e));
            }
        });
    }

    /**
     * 只处理是eClass类型的异常
     */
    public <TE extends Throwable> Try<T, E> caught(@NotNull Class<TE> eClass, @NotNull Func<TE, T, E> f) {
        Objects.requireNonNull(eClass);
        Objects.requireNonNull(f);
        assertExecuted();
        return new Try<>(() -> {
            this.executed = true;
            try {
                return source.get();
            } catch (Throwable e) {
                if (eClass.isInstance(e)) {
                    TE te = (TE) e;
                    return f.apply(te);
                }
                throw ((E) e);
            }
        });
    }

    /**
     * 只处理不是eClass类型的异常
     */
    public <TE extends Throwable> Try<T, E> caughtElse(@NotNull Class<TE> eClass, @NotNull Func<E, T, E> f) {
        Objects.requireNonNull(eClass);
        Objects.requireNonNull(f);
        assertExecuted();
        return new Try<>(() -> {
            this.executed = true;
            try {
                return source.get();
            } catch (Throwable e) {
                if (eClass.isInstance(e)) {
                    throw e;
                }
                return f.apply(((E) e));
            }
        });
    }


    public Try<T, E> finalize(@NotNull Run<E> run) {
        Objects.requireNonNull(run);
        assertExecuted();
        return new Try<>(() -> {
            try {
                return this.source.get();
            } catch (Throwable e) {
                throw Errors.harmless(e);
            } finally {
                run.run();
            }
        });
    }


    public T ret() {
        assertExecuted();
        try {
            return source.get();
        } catch (Throwable e) {
            throw Errors.harmless(e);
        }
    }

    /**
     * 忽略报错信息
     */
    public Option<T> ignore() {
        assertExecuted();
        try {
            T v = source.get();
            return Option.some(v);
        } catch (Throwable e) {
            return Option.none();
        }
    }

    public Trouble<T, E> solve() {
        assertExecuted();
        try {
            T v = source.get();
            return Trouble.ok(v);
        } catch (Throwable e) {
            return Trouble.err(((E) e));
        }
    }

    public static class RunTry<E extends Throwable> {

        private final Run<E> task;
        private volatile boolean executed = false;

        protected RunTry(Run<E> task) {
            this.task = task;
        }

        private void assertExecuted() {
            if (executed) throw new IllegalStateException("Try has already been executed");
        }

        public RunTry<E> then(@NotNull Run<E> run) {
            Objects.requireNonNull(run);
            assertExecuted();
            return new RunTry<>(() -> {
                this.executed = true;
                this.task.run();
                run.run();
            });
        }

        public <T> Try<T, E> then(@NotNull Supply<T, E> supply) {
            Objects.requireNonNull(supply);
            assertExecuted();
            return new Try<>(() -> {
                this.executed = true;
                this.task.run();
                return supply.get();
            });
        }

        public <EE extends E> RunTry<E> caught(@NotNull Class<EE> eClass, @NotNull Consume<EE, E> consumer) {
            Objects.requireNonNull(eClass);
            Objects.requireNonNull(consumer);
            assertExecuted();
            return new RunTry<>(() -> {
                this.executed = true;
                try {
                    this.task.run();
                } catch (Throwable e) {
                    if (eClass.isInstance(e)) {
                        consumer.accept(((EE) e));
                    }
                    throw ((E) e);
                }
            });
        }

        public RunTry<E> caught(@NotNull Consume<E, E> consumer) {
            Objects.requireNonNull(consumer);
            assertExecuted();
            return new RunTry<>(() -> {
                this.executed = true;
                try {
                    this.task.run();
                } catch (Throwable e) {
                    consumer.accept(((E) e));
                }
            });
        }

        public RunTry<E> finalize(@NotNull Run<E> run) {
            Objects.requireNonNull(run);
            assertExecuted();
            return new RunTry<>(() -> {
                try {
                    this.task.run();
                } catch (Throwable e) {
                    throw Errors.harmless(e);
                } finally {
                    run.run();
                }
            });
        }

        public void run() {
            assertExecuted();
            try {
                this.task.run();
            } catch (Throwable e) {
                throw Errors.harmless(e);
            }
        }

        /**
         * 忽略报错信息
         */
        public void ignore() {
            assertExecuted();
            try {
                this.task.run();
            } catch (Throwable ignored) {
            }
        }

        /**
         * 处理报错信息
         */
        public Trouble<Void, E> solve() {
            assertExecuted();
            try {
                this.task.run();
                return Trouble.nil();
            } catch (Throwable e) {
                return Trouble.err(((E) e));
            }
        }
    }

    public static <T, E extends Throwable> Try<T, E> valueOf(@NotNull Supply<T, E> source) {
        Objects.requireNonNull(source);
        return new Try<>(source);
    }

    public static <E extends Throwable> RunTry<E> runOf(@NotNull Run<E> run) {
        Objects.requireNonNull(run);
        return new RunTry<>(run);
    }

    public static <T, E extends Throwable> T block(@NotNull Supply<T, E> source) {
        Objects.requireNonNull(source);
        try {
            return source.get();
        } catch (Throwable e) {
            throw Errors.harmless(e);
        }
    }

    public static <T, E extends Throwable> T block(@NotNull Supply<T, E> source, Function<Throwable, E> errorCreate) throws E {
        Objects.requireNonNull(source);
        Objects.requireNonNull(errorCreate);
        try {
            return source.get();
        } catch (Throwable e) {
            throw errorCreate.apply(e);
        }
    }
}
